using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace SolarWinds.Input
{
    public delegate void MouseEventHandler(Object sender, MouseEvent e);

    public class MouseManager : GameComponent
    {
        public event MouseEventHandler ButtonPressed;
        public event MouseEventHandler ButtonReleased;
        public event MouseEventHandler ButtonClicked;
        public event MouseEventHandler ButtonDoubleClicked;
        public event MouseEventHandler Moved;
        public event MouseEventHandler WheelScrolled;

        /// <summary>
        /// List with all MouseButtons.
        /// </summary>
        readonly MouseButtons[] mMouseButtons;

        /// <summary>
        /// For each mouse button we store the time when it has been pressed and released last.
        /// </summary>
        private readonly Dictionary<MouseButtons, TimeSpan> mDictButton2PressedTime = new Dictionary<MouseButtons, TimeSpan>();
        private readonly Dictionary<MouseButtons, TimeSpan> mDictButton2ReleasedTime = new Dictionary<MouseButtons, TimeSpan>();
        private readonly Dictionary<MouseButtons, bool> mDictButton2State = new Dictionary<MouseButtons, bool>(); // true = down and false = up state

        public MouseManager(Game game)
            : base(game)
        {
            mMouseButtons = (MouseButtons[])Enum.GetValues(typeof(MouseButtons));
        
            InitializeButtonList();
        }

        private void InitializeButtonList()
        {
            foreach (MouseButtons button in mMouseButtons)
            {
                mDictButton2PressedTime.Add(button, TimeSpan.Zero);
                mDictButton2ReleasedTime.Add(button, TimeSpan.Zero);
                mDictButton2State.Add(button, false);
            }
        }

        Point mOldAbsPos = new Point();
        int mOldWheelPos = 0;

        public override void  Update(GameTime gameTime)
        {
            base.Update(gameTime);
 
            TimeSpan time = gameTime.TotalGameTime;
            TimeSpan elapsedTime = gameTime.ElapsedGameTime;

            InputModifiers modifiers = InputManager.GetModifiers();
            MouseState mouseState = Mouse.GetState();

            Point absPos = new Point (mouseState.X, mouseState.Y);
            Point relPos = new Point (absPos.X - mOldAbsPos.X, absPos.Y - mOldAbsPos.Y);

            int absWheelPos = mouseState.ScrollWheelValue;
            int relWheelPos = absWheelPos - mOldWheelPos;

            // buttons changed
            foreach (MouseButtons button in mMouseButtons)
            {
                if (mDictButton2State[button] == false &&
                    IsButtonInState(button, ButtonState.Pressed))
                {
                    // button has been pressed since last update
                    mDictButton2PressedTime[button] = time;
                    mDictButton2State[button] = true;

                    OnButtonPressed(new MouseEvent(time, modifiers, 
                                                   absPos, relPos, absWheelPos, relWheelPos,
                                                   button));
                }
                else if (mDictButton2State[button] &&
                         IsButtonInState(button, ButtonState.Released))
                {
                    // button has been released since last update
                    mDictButton2ReleasedTime[button] = time;
                    mDictButton2State[button] = false;

                    OnButtonReleased(new MouseEvent(time, modifiers, 
                                                    absPos, relPos, absWheelPos, relWheelPos,
                                                    button));

                    // check if it was fast enough for a click
                    if (mDictButton2ReleasedTime[button] - mDictButton2PressedTime[button] < new TimeSpan(0, 0, 1))
                    {
                        OnButtonClicked(new MouseEvent(time, modifiers, 
                                                       absPos, relPos, absWheelPos, relWheelPos,
                                                       button));
                    }
                }
                else if (mDictButton2State[button] &&
                         IsButtonInState(button, ButtonState.Pressed))
                {
                    // button is still pressed
                    // Somthing like this OnKeyStillPressed(new KeyEvent(time, modifiers, key));
                }
                else // button is still released
                {
                    // nothing to to
                }
            }

            // mouse moved
            if (relPos.X != 0 || relPos.Y != 0)
            {
                OnMoved(new MouseEvent(time, modifiers, 
                                       absPos, relPos, absWheelPos, relWheelPos, 
                                       MouseButtons.None));
            }

            // wheel changed
            if (relWheelPos != 0)
            {
                OnWheelScrolled(new MouseEvent(time, modifiers,
                                               absPos, relPos, absWheelPos, relWheelPos,
                                               MouseButtons.None));
            }

            mOldWheelPos = mouseState.ScrollWheelValue;
            mOldAbsPos = absPos;
        }

        

        private static bool IsButtonInState(MouseButtons button, ButtonState state)
        {
            // TODO speed this up with a dictionary lookup

            if (button == MouseButtons.Left)
            {
                return Mouse.GetState().LeftButton == state;
            }
            else if (button == MouseButtons.Middle)
            {
                return Mouse.GetState().MiddleButton == state;
            }
            else if (button == MouseButtons.Right)
            {
                return Mouse.GetState().RightButton == state;
            }
            else if (button == MouseButtons.XButton1)
            {
                return Mouse.GetState().XButton1 == state;
            }
            else if (button == MouseButtons.XButton2)
            {
                return Mouse.GetState().XButton2 == state;
            }
            else if (button == MouseButtons.None)
            {
                return false;
            }

            return false;
        }

        private void OnButtonPressed(MouseEvent evt)
        {
            if (ButtonPressed != null)
            {
                ButtonPressed(this, evt);
            }
        }

        private void OnButtonReleased(MouseEvent evt)
        {
            if (ButtonReleased != null)
            {
                ButtonReleased(this, evt);
            }
        }

        private void OnButtonClicked(MouseEvent evt)
        {
            if (ButtonClicked != null)
            {
                ButtonClicked(this, evt);
            }
        }

        private void OnButtonDoubleClicked(MouseEvent evt)
        {
            if (ButtonDoubleClicked != null)
            {
                ButtonDoubleClicked(this, evt);
            }
        }

        private void OnMoved(MouseEvent evt)
        {
            if (Moved != null)
            {
                Moved(this, evt);
            }
        }

        private void OnWheelScrolled(MouseEvent evt)
        {
            if (WheelScrolled != null)
            {
                WheelScrolled(this, evt);
            }
        }
    }
}